开发者说|Apollo介绍之Cyber设计
需要一个什么样的系统? 如何保证系统的稳定性和灵活性? 如何来调试和维护这样复杂的系统?
一些机器人搭载多台计算机,每台计算机用于控制机器人的部分驱动器或传感器; 即使只有一台计算机,通常仍将程序划分为独立运行且相互协作的小的模块来完成复杂的控制任务,这也是常见的做法; 当多个机器人需要协同完成一个任务时,往往需要互相通信来支撑任务的完成。
单计算机或者多计算机不同进程间的通信问题是上述例子中的主要挑战。ROS为实现上述通信提供两种相对简单、完备的机制。
ROS 标准包(Standard Packages)提供稳定、可调式的各类重要机器人算法实现; ROS通信接口正在成为机器人软件互操作的事实标准,也就是说绝大部分最新的硬件驱动和最前沿的算法实现都可以在ROS中找到。例如,在ROS的官方网页上有着大量的开源软件库,这些软件使用ROS通用接口,从而避免为了集成它们而重新开发新的接口程序。
综上所述,开发人员将更多的时间用于新思想和新算法的设计与实现,尽量避免重复实现已有的研究结果。
快速测试为机器人开发软件比其他软件开发更具挑战性,主要是因为调试准备时间长,且调试过程复杂。况且,因为硬件维修、经费有限等因素,不一定随时有机器人可供使用。ROS提供两种策略来解决上述问题:
精心设计的ROS系统框架将底层硬件控制模块和顶层数据处理与决策模块分离,从而可以使用模拟器替代底层硬件模块,独立测试顶层部分,提高测试效率; ROS 另外提供了一种简单的方法可以在调试过程中记录传感器数据及其他类型的消息数据,并在试验后按时间戳回放。通过这种方式,每次运行机器人可以获得更多的测试机会。例如,可以记录传感器的数据,并通过多次回放测试不同的数据处理算法。在 ROS 术语中,这类记录的数据叫作包(bag),一个被称为rosbag的工具可以用于记录和回放包数据; 用户通常通过台式机、笔记本或者移动设备发送指令控制机器人,这种人机交互接口可以认为是机器人软件的一部分。
采用上述方案的一个最大优势是实现代码的“无缝连接”,因为实体机器人、仿真器和回放的包可以提供同样(至少是非常类似)的接口,上层软件不需要修改就可以与它们进行交互,实际上甚至不需要知道操作的对象是不是实体机器人。
实际上Apollo主要用到了ROS消息通信的功能,同时也用到了录制bag包等一些工具类。所以目前Cyber的首要设计就是替换ROS消息通信的功能。
上述的系统是一个分布式系统,每个节点作为一个Node; 上述系统每个节点之间都可以相互通信,一个节点下线,不会导致整个系统瘫痪; 上述系统可以灵活的增加、删除节点。
上述系统是一个分布式系统,每个节点作为一个Node; 上述系统每个节点通过主节点通信,主节点下线会导致系统崩溃; 上述系统可以灵活的增加、删除节点。
这2种方式的主要区别就是通信方式的区别。
当一个节点有10s没有发送消息,那么集中式的消息可以监控并且知道这个节点是否出故障了; 集中式的消息可以知道哪些节点在线去找到这些节点,这在多机网络通信的时候很管用,节点只需要注册自己的IP地址,然后由管理节点告诉你去哪里拿到消息。
上述只是一个初步的想法,那么基于上面的启发,针对上述的每项需求,完成的系统设计。
多节点
节点管理 节点依赖
通信方式
点对点 采用共享内存的方式可以提高效率,需要注意并发访问时候的问题
资源调度
进程调度算法改为实时算法:Linux目前的调度是Completely Fair Scheduling(CFS) 算法,需要改为实时的调度算法 进程有优先级 支持并发 能够限制系统的资源占用
Linux进程调度
无人驾驶线程调度
首先,我们假设定位、感知、规划、控制和传感器读取的优先级比日志和地图更高。也可以理解为日志和地图读取的慢点对系统的影响并不大,而上述的模块如果读取的很慢,则会导致系统故障。
接下来我们再看优先级高的模块,因为目前我们只有两个核心,所以不可能同时执行上述所有模块,只能通过时间片轮转来实现。这里就引入了一个问题,如果分配的时间片太长,会导致响应不及时;如果分配的时间片太短,又会导致线程切换开销,需要折中考虑。如果运行过程中感知和规划正在执行,并且分配的时间片还没有用完,那么控制模块不会抢占CPU,直到运行中的模块时间片用完。
对这些模块的算法复杂度有要求,如果感知模块采用了复杂度较高的算法提高准确率,这样导致的结果是感知会占用更多的CPU时间,其他模块每次需要和感知模块竞争CPU,结果就是导致总体的执行时间会变长。比如,规定感知只需要在200ms的时候处理完任务就可以了,之前感知的算法实现是100ms,而控制模块的时间是100ms,CPU的时间片是50ms,那么感知需要2个时间片,控制需要2个时间片,总的需要时间是200ms,控制模块完成的时间由于时间片轮转,可能是150ms。但如果感知为了提高效果,增加了算法的复杂度,运行时间改为200ms,感知模块照常能够完成自己的任务,因为只要200ms完成任务,感知模块就完成了任务,总的需要的时间可能是300ms,但是引入的另外的问题是由于竞争控制模块可能完成的时间是200ms,这样就会导致控制模块的时延达不到要求。其实这样的情况总的来说一是需要升级硬件,比如增加CPU的核数;另外的办法就是降低系统算法的复杂度,每个模块的任务要尽可能的高效。
通过上面的要求也可以看到,系统进程的算法复杂度要尽可能的稳定,不能一下子是50ms,一下子是200ms,或者直接找不到最优解,这是最坏的情况,如果各个模块的算法都不太稳定,带来的影响就是当遇到极端情况,每个模块需要的时间都变多时,系统的负载会一下子变高,导致模块的响应不及时,这对自动驾驶是很致命的问题。
上述是理想情况下,那么我们会遇到哪些情况,系统的进程会崩溃或者一直占用CPU的情况呢?
找不到最优解,死循环。大部分情况下程序没有响应是因为找不到最优解,或者死循环,这种状态可以通过代码和算法实现保证;
堆栈溢出,内存泄露,空指针。这种情况是由于程序编写错误,也可以通过代码保证;
硬件错误。极小概率的情况下,CPU的寄存器会出错,嵌入式(PowerPC)的CPU都会有冗余校正,而家用或者服务器(Intel)没有这种设计,这种情况下只能重启进程,或者硬件。
根据上述的思路,可以得到如下图所示:
把控制的优先级设置到最高,规划其次,感知和定位的优先级设置相对较低,因为控制和规划必须马上处理,感知如果当前帧处理不过来,大不了就丢掉,接着处理下一帧。当然这些线程都需要设置为实时进程。而地图、日志、定位等的优先级设置较低,在其他高优先级的进程到来时候会被抢占;
Canbus等传感器数据,可以绑定到一个CPU核心上处理,这样中断不会影响到其他核心,导致频繁线程切换;
对线程设置Cgroups,可以控制资源使用,设置优先级等;
测试算法的时间复杂度,是否稳定。
Linux自带了perf可以采样一段时间的系统调用,输出文件再结合火焰图,可以查看当前系统的调用情况,各个线程对CPU的使用情况,然后进行优化。
软件复用
包管理
工具类
快速测试
人机交互
日志
调试功能
通信接口